//============================================================================
// Name        : ex5_server.cpp
// Author      : amirlaz, afief
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#define FILE_NAME "twitServer.log"
#include "twitServer.h"
#define SUCCESS_MESSAGE "Connected successfully\n"
#define USAGE "Usage: twitServer <Port>\n"
using namespace std;
typedef pair<string,Client*> ClientPair;
typedef pair<int,Client*> ClientPairByFd;

/***
 * wrapper around the library sendMsg, this one
 * knows what to do in case of an error
 * returns -1 in case of an error
 */



void Server::ServermainError(string msg, bool toDo)
{
	cerr << "ERROR on: server- "<< msg <<endl;
	log << "ERROR on: server- "<< msg <<endl;
	if (toDo)
	{
		shutdown();
		exit(1);
	}

}

string Server::createMessagePrefix(string fromClient)
{
	stringstream prefix;
	time_t rawtime;
	struct tm * timeinfo=NULL;
	time ( &rawtime );
	timeinfo = localtime ( &rawtime );
	if (timeinfo==NULL)
	{
		ServermainError("System function failes",true);
	}
	prefix << timeinfo->tm_hour << ":" << timeinfo->tm_min << "::" << fromClient << " - ";
	return prefix.str();

}

bool Server::sendMessage(int fd, string message)
{
	if (sendMsg(fd,message)==FAILURE)
	{
		cerr << "ERROR Sending to client " + _clientsByFd.find(fd)->second->getName()
				+ " failed\n";
		log << "ERROR Sending to client " + _clientsByFd.find(fd)->second->getName()
						+ " failed\n";
		removeClientByFd(fd);
		return false;
	}
	return true;
}




void Server::removeClientByFd(int fd)
{
	//TODO:  wait for this fd's threads to return???
	ClientsByFd::iterator itByFd= _clientsByFd.find(fd);
	FD_CLR(fd,&_listenSet);
	_clients.erase(itByFd->second->getName());

	// erasing followers //

	for (ClientsList::iterator it=_clients.begin(); it!=_clients.end(); ++it)
	{
		it->second->Unfollow(itByFd->second->getName());
	}

	// END erasing followers

	delete itByFd->second;
	_clientsByFd.erase(itByFd);





	// shut down all threads??
	// mutex Data unlock
}

void Server::shutdown()
{
	for (ClientsList::iterator it = _clients.begin();it!=_clients.end();++it)
	{
		SendServerMessage("ServerDis\n",it->second);
		int fd=it->second->getFd();
		close (fd);
		removeClientByFd(fd);
		log.close();
	}
	// wait for all sends
	//
	// remove all clients
	// kill all sockets
}

void Server::SendErrorToUknown (int fd, string msg,bool toClose)
{
	//TODO: create a new thread (it doesnt have to be remembered)
	sendMsg(fd,msg); // dont care if it fails because he is not a valid user and
	// I already wrote about this to stderr and to the log

	if (toClose)
		close(fd);
}
bool Server::SendServerMessage(string msg,Client* cl)
{
	//TODO: create new thread and add to queue or create threadpool and dispatch
	// the pointer to the function should be as followed

	// lock -- cl->mutex()
	//cout << "sending: " << SUCCESS_MESSAGE;
	return sendMessage(cl->getFd(),msg);
	// unlock -- cl->unlockMutex
}


void Server::insertNewClient()
{
	int newSocket = accept(_listenPort);
	if (newSocket == -1)
	{
		ServermainError("System call failed", true);
	}

	string name; int res;

	res = recvMsg(newSocket, name);
	name.replace(name.find("\n"),1,"");
	if (res!=FAILURE)
	{
		//pair<ClientsList::iterator,bool> ret;
		//ret=_clients.insert()
		ClientsList::iterator it=_clients.find(name);
		if(it==_clients.end())
		{
			// mutex data lock
			Client* cl=new Client(newSocket,name);
			_clients.insert(ClientPair(name,cl));
			_clientsByFd.insert(ClientPairByFd(newSocket,cl));
			_current_max_fd = _current_max_fd > newSocket ? _current_max_fd : newSocket; //updating the max
			FD_SET(newSocket, &_listenSet);
			// mutex data unlock
			log << name <<"\t" <<"connected.\n";
			SendServerMessage(SUCCESS_MESSAGE,cl);

		}
		else
		{
			//cout << "WANNA BAND CLIENT" << endl;
			SendErrorToUknown(newSocket,"ERROR client already exist\n",false);
			//cerr << "ERROR client already exist\n";
			log  << "ERROR client already exist\n";
			SendErrorToUknown(newSocket,"ERROR failed to connect\n",false);
			close(newSocket);
			//SendErrorToUknown(newSocket,"ERROR client already exist\n",false);

		}
	} else {
		close(newSocket); //receive failed or disconnected
		// no need to show message, we stringdon't have client name
	}
}

bool Server::DirectMessage(int current_fd,string &msg)
{
	size_t namePos = msg.find_first_of('@',0);
	string message,name;
	Client* from=_clientsByFd.find(current_fd)->second;
	name=msg.substr(0,namePos);
	message=msg.substr(namePos+1,msg.length());

	if (message.substr(message.length()-1,message.length())!="\n" || name.length()==0)
		return false;
	log << from->getName() << "\t" << "direct message " << name <<"\t" << msg.substr(0,msg.length()-1) << ".\n";

	ClientsList::iterator it = _clients.find(name);
	if (it == _clients.end())
	{
		SendServerMessage("Error: "+name+" does not exist\n",from);
		log << "Error: "+name+" does not exist\n";
		//cerr << "Error: "+name+" does not exist\n";
		return true; // because the format is good
	}
	string out=createMessagePrefix(from->getName());
	out+="@"+it->second->getName()+"--"+message;
	SendServerMessage(out,it->second);
	return true;

}

bool Server::Who(int current_fd,string &msg)
{

	//TODO: if we move to threads, lock here
	unsigned int i=1;
	string out;
	Client* from=_clientsByFd.find(current_fd)->second;

	log << from->getName() << "\t"<< "Who.\n" ;
	if (msg!="WHO\n") return false;


	for (ClientsList::iterator it = _clients.begin(); it!= _clients.end();++it,i++)
	{
		out += it->second->getName()+"\t ";
	}

	out[out.length()-1]='\n';
	sendMessage(current_fd,out);
	return true;
	// unlock here
}

bool Server::Disconnect(int current_fd,string &msg)
{

	if (msg!="Dis\n") return false;

	Client* from=_clientsByFd.find(current_fd)->second;
	log << from->getName()<<"\tdisconnect." << endl;
	//cout << _clientsByFd.find(current_fd)->second->getName()<<" has disconnected" << endl;
	removeClientByFd(current_fd);
	close(current_fd);
	return true;

}

bool Server::Follow(int current_fd,string &name)
{
	if (name[name.length()-1]!='\n') return false;
	name=name.substr(0,name.length()-1);
	Client* from=_clientsByFd.find(current_fd)->second;
	log << from->getName() <<"\tfollowed "<< name <<"."<<endl;


	ClientsList::iterator it = _clients.find(name);
	if (it == _clients.end())
	{
		log << "Error: "+name+" does not exist\n";
		SendServerMessage("Error: "+name+" does not exist\n",_clientsByFd.find(current_fd)->second);

		return true;
	}
	//cout << _clientsByFd.find(current_fd)->second->getName() << "\t followed " << name << endl;
	it->second->Follow(_clientsByFd.find(current_fd)->second);
	return true;
}

bool Server::Unfollow(int current_fd,string &name)
{
	if (name[name.length()-1]!='\n') return false;
	name=name.substr(0,name.length()-1);

	Client* from=_clientsByFd.find(current_fd)->second;
	log << from->getName() <<"\tunfollowed "<< name <<"."<<endl;

	ClientsList::iterator it = _clients.find(name);
	if (it == _clients.end())
	{
		log << "Error: "+name+" does not exist\n";
		SendServerMessage("Error: "+name+" does not exist\n",_clientsByFd.find(current_fd)->second);
		return true;
	}
	//TODO: should be printed to log!!! //

	//cout << _clientsByFd.find(current_fd)->second->getName() << "\t unfollowed " << name << endl;
	it->second->Unfollow(_clientsByFd.find(current_fd)->second->getName());
	return true;
}


bool Server::Twit(int current_fd,string &msg)
{
	if (msg[msg.length()-1]!='\n') return false;
	Client* cl=_clientsByFd.find(current_fd)->second;

	Client* from=_clientsByFd.find(current_fd)->second;
	log << from->getName() <<"\ttwitted "<< msg.substr(0,msg.length()-1) << ".\n";

	for (map<string,Client*>::iterator it=cl->followers_begin(); it!=cl->followers_end();++it)
	{
		string out=createMessagePrefix(_clientsByFd.find(current_fd)->second->getName()) + msg;
		SendServerMessage(out,it->second);
	}
	return true;
}

bool Server::InterpretCommand(int current_fd,string &msg)
{
	string command, rest;

	size_t commandPos = msg.find_first_of(' ',0);

	command = msg.substr(0,commandPos);
	rest = msg.substr(commandPos+1,msg.length());
	//cout << command << endl << rest << endl;
	bool isLegal=false;
	bool isRestLegal=false;
	if ( command == "DM")
	{
		isRestLegal=DirectMessage(current_fd,rest);
		isLegal=true;
	}
	else if( command=="WHO\n")
	{
		isRestLegal=Who(current_fd,rest);
		isLegal=true;
	}
	else if (command=="Dis\n") //disconnect
	{
		isRestLegal=Disconnect(current_fd,rest);
		isLegal=true;
	}
	else if(command=="FOLLOW")
	{
		isRestLegal=Follow(current_fd,rest);
		isLegal=true;
	}
	else if(command=="UNFOLLOW")
	{
		isRestLegal=Unfollow(current_fd,rest);
		isLegal=true;
	}
	else if(command=="TWIT")
	{
		isRestLegal=Twit(current_fd,rest);
		isLegal=true;
	}
	return isLegal && isRestLegal;

}



void Server::startServer()
{
	log.open(FILE_NAME,ios_base::app | ios_base::out);
	init_tcpwrapper(&log);

	_listenPort=listen(_port);
	_current_max_fd=_listenPort;


	FD_SET(_listenPort, &_listenSet);
	FD_SET(STDIN_FILENO, &_listenSet);
	while(true)
	{
		fd_set readfds = _listenSet; //for select to listen and change
		// select listens to all sockets and stdin
		if (select(_current_max_fd+1, &readfds, NULL, NULL, NULL) == -1)
		{
			ServermainError("System function error", true);
		}

		if (FD_ISSET(_listenPort, &readfds))
		{
			insertNewClient();

		}else if (FD_ISSET(STDIN_FILENO, &readfds))
		{
			// read from stdin
			string msg;
			int stdin_res=recvMsg(STDIN_FILENO, msg);
			if (stdin_res==FAILURE)
			{
				ServermainError("stdin error",true);
			}
			else
			{
				if (msg=="EXIT\n")
				{
					shutdown();
					return;
				}
				else
				{
					cerr << "ERROR: Unknown command\n";
					log << "ERROR: Unknown command\n";
					// no commands other than EXIT are supported
				}
			}
		} else

			for (ClientsList::iterator it = _clients.begin(); it!=_clients.end(); ++it)
			{
				// finding existing fd to read from
				int currentFd = it->second->getFd();
				if (FD_ISSET(currentFd, &readfds))
				{

					{
						string msg;
						//cout << "server- client exists\n";
						int res = recvMsg(currentFd, msg); // we dont care if this returns -1
						if (res==FAILURE) // the client has disconnected or error
						{
							cerr <<"ERROR " << _clientsByFd.find(currentFd)->second->getName() << " disconnected\n";
							log <<"ERROR " << _clientsByFd.find(currentFd)->second->getName() << " disconnected\n";
							removeClientByFd(currentFd); //SHOULD I ???

						}
						else
						{
							if (InterpretCommand(currentFd,msg)==false) // the method disconnected the client
							{
								cerr << "ERROR Wrong command or command format\n";
								log << "ERROR Wrong command or command format\n";
								SendServerMessage("ERROR Wrong command or command format\n",
										_clientsByFd.find(currentFd)->second);
							}
						}
					}
				}
			}
	}
	shutdown();

	return;
}


int main(int argc, const char **argv) {
	int port=0;
	if (argc!=2) {
		cerr << USAGE;
		return 1;
	}
	port = (atoi(argv[1]));

	if (port < 1024 || port >65536)
	{

		cerr << "port should be in range: 1024<port<65536\n";
		return 1;
	}

	if (port==0)
	{
		cerr << "Illegal port number\n";
		exit(FAILURE);
	}
	Server server(port);
	server.startServer();
	cout << "Server EXITed normally" << endl; // prints !!!Hello World!!!
	return 0;
}
